﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Threading;
using MyFramework.Common;
using MyFramework.Common.Enums;
using MyFramework.Common.Interfaces;

namespace MyFramework.Core
{
    /// <summary>
    /// controller base
    /// </summary>
    public abstract class ControllerBase : Entity, IController
    {
        #region Fields

        /// <summary>
        /// The controller unique identifier
        /// </summary>
        private Guid controllerId;

        /// <summary>
        /// The type
        /// </summary>
        private Type type;

        /// <summary>
        /// The view model
        /// </summary>
        private IViewModel viewModel;

        /// <summary>
        /// The state
        /// </summary>
        private ControllerState state;

        /// <summary>
        /// The action names
        /// </summary>
        private string[] actionNames;

        /// <summary>
        /// The startup action executed
        /// </summary>
        private bool startupActionExecuted;

        /// <summary>
        /// Gets or sets the mapped actions.
        /// </summary>
        /// <value>
        /// The mapped actions.
        /// </value>
        internal List<Tuple<string, Action<object[]>, Func<object[], bool>>> MappedActions { get; set; }

        /// <summary>
        /// The default exception handler
        /// </summary>
        public static Func<Exception, bool> DefaultExceptionHandler;

        #endregion

        #region ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="ControllerBase"/> class.
        /// </summary>
        protected ControllerBase()
        {
            this.Dispatcher = Application.Current.Dispatcher;
            this.ControllerId = Guid.NewGuid();
            this.State = ControllerState.NotInitialized;
            this.CustomPropertyBag = new CustomPropertyCollection();
            this.MappedActions = new List<Tuple<string, Action<object[]>, Func<object[], bool>>>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the initialize parameters.
        /// </summary>
        /// <value>
        /// The initialize parameters.
        /// </value>
        protected IDictionary<string, object> InitParameters { get; private set; }

        /// <summary>
        /// Gets the controller id.
        /// </summary>
        /// <value>
        /// The controller id.
        /// </value>
        public Guid ControllerId
        {
            get
            {
                return this.controllerId;
            }
            private set
            {
                if (this.controllerId != value)
                {
                    this.controllerId = value;
                    this.RaisePropertyChangedEvent(() => ControllerId);
                }
            }
        }

        /// <summary>
        /// Gets or sets the name of the logical.
        /// </summary>
        /// <value>
        /// The name of the logical.
        /// </value>
        public Type Type
        {
            get
            {
                this.EnsureNotDisposed();

                return this.type;
            }
            set
            {
                this.EnsureNotDisposed();

                if (this.type != value)
                {
                    this.type = value;
                    this.RaisePropertyChangedEvent(() => Type);
                }
            }
        }

        /// <summary>
        /// Gets the view model.
        /// </summary>
        /// <value>
        /// The view model.
        /// </value>
        public IViewModel ViewModel
        {
            get
            {
                this.EnsureNotDisposed();

                return this.viewModel;
            }
            private set
            {
                if (this.viewModel != value)
                {
                    this.viewModel = value;
                    this.RaisePropertyChangedEvent(() => ViewModel);
                }
            }
        }

        /// <summary>
        /// Gets or sets the state.
        /// </summary>
        /// <value>
        /// The state.
        /// </value>
        public ControllerState State
        {
            get
            {
                return this.state;
            }
            set
            {
                if (this.state != value)
                {
                    this.state = value;
                    this.RaisePropertyChangedEvent(() => State);
                }
            }
        }

        /// <summary>
        /// Gets the action names.
        /// </summary>
        /// <value>
        /// The action names.
        /// </value>
        public string[] ActionNames
        {
            get
            {
                this.EnsureNotDisposed();

                return this.actionNames;
            }
            private set
            {
                if (this.actionNames != value)
                {
                    this.actionNames = value;
                    this.RaisePropertyChangedEvent(() => ActionNames);
                }
            }
        }

        /// <summary>
        /// Gets the custom property bag.
        /// </summary>
        /// <value>
        /// The custom property bag.
        /// </value>
        public CustomPropertyCollection CustomPropertyBag { get; private set; }

        /// <summary>
        /// Gets the dispatcher.
        /// </summary>
        /// <value>
        /// The dispatcher.
        /// </value>
        public Dispatcher Dispatcher { get; private set; }

        #endregion

        #region Methods

        /// <summary>
        /// Creates the view model.
        /// </summary>
        /// <returns></returns>
        protected abstract IViewModel CreateViewModel();

        /// <summary>
        /// Called when [initialized].
        /// </summary>
        protected virtual void OnInitialized()
        {
        }

        /// <summary>
        /// Caches the metadata.
        /// </summary>
        /// <param name="metadata">The metadata.</param>
        protected virtual void CacheMetadata(ControllerMetadata metadata)
        {
        }

        /// <summary>
        /// Documents the action core.
        /// </summary>
        /// <param name="actionName">Name of the action.</param>
        /// <param name="actionParameters">The action parameters.</param>
        /// <param name="throwIfActionNotFound">if set to <c>true</c> [throw difference action not found].</param>
        /// <param name="exceptionMessageFunc">The exception message function.</param>
        /// <returns></returns>
        /// <exception cref="MyFramework.Core.ControllerException"></exception>
        private bool DoActionCore(string actionName, object[] actionParameters, bool throwIfActionNotFound, Func<Exception, Exception> exceptionMessageFunc)
        {
            try
            {
                Debug.WriteLine(" °°°°°°°°°°°° DoActionCore --> " + actionName);
                var item = this.MappedActions.FirstOrDefault(t => t.Item1 == actionName);
                if (item != null)
                {
                    try
                    {
                        this.Dispatcher.Invoke(new Action(() =>
                        {
                            Debug.WriteLine(" °°°°°°°°°°°° DoActionCore --> " + actionName + " --> PRE BUSY");
                            this.State = ControllerState.Busy;
                            Debug.WriteLine(" °°°°°°°°°°°° DoActionCore --> " + actionName + " --> POST BUSY");
                        }), DispatcherPriority.Normal);

                        this.Dispatcher.Invoke(new Action(() =>
                        {
                            Debug.WriteLine(" °°°°°°°°°°°° DoActionCore --> " + actionName + " --> PRE INVOKE");
                            item.Item2(actionParameters);
                            Debug.WriteLine(" °°°°°°°°°°°° DoActionCore --> " + actionName + " --> POST INVOKE");
                        }), DispatcherPriority.Background);

                        return true;
                    }
                    finally
                    {
                        this.Dispatcher.Invoke(new Action(() =>
                        {
                            Debug.WriteLine(" °°°°°°°°°°°° DoActionCore --> " + actionName + " --> PRE READY - CURRENT STATE " + this.State);
                            if (this.State < ControllerState.Disposing)
                            {
                                this.State = ControllerState.Ready;
                            }
                            Debug.WriteLine(" °°°°°°°°°°°° DoActionCore --> " + actionName + " --> POST READY");
                        }), DispatcherPriority.Normal);
                    }
                }
                else
                {
                    if (throwIfActionNotFound)
                    {
                        throw new ControllerException(string.Format(Resources.ActionNotRegistered, actionName));
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            catch (Exception ex)
            {
                var handled = ExceptionHandler.HandleWithExceptionHandler(ref ex, exceptionMessageFunc, DefaultExceptionHandler);
                if (!handled)
                {
                    throw ex;
                }
                else
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// Handles the CollectionChanged event of the Views control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        private void Views_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (this.ViewModel.Views.Count == 0)
            {
                this.Dispose();
            }
        }

        /// <summary>
        /// Ensures the not disposed.
        /// </summary>
        /// <exception cref="MyFramework.Core.ControllerDisposedException"></exception>
        private void EnsureNotDisposed()
        {
            if (this.State == ControllerState.Disposed)
            {
                throw new ControllerDisposedException(this.GetType().FullName, Resources.NotAvailableOnDisposedController.FormatEx(this.ControllerId));
            }
        }

        /// <summary>
        /// Initializes the specified initialize parameters.
        /// </summary>
        /// <param name="initParameters">The initialize parameters.</param>
        public void Initialize(IDictionary<string, object> initParameters)
        {
            this.State = ControllerState.Initializing;

            this.InitParameters = initParameters;

            IViewModel createdViewModel = CreateViewModel();

            createdViewModel.Initialize(initParameters);
            createdViewModel.Views.CollectionChanged += Views_CollectionChanged;
            createdViewModel.RaiseViewModelReady();
            createdViewModel.SetIndexMainView();
            this.ViewModel = createdViewModel;

            this.CacheMetadata(new ControllerMetadata(this));
            this.ActionNames = this.MappedActions.Select(a => a.Item1).ToArray();

            this.OnInitialized();
            this.State = ControllerState.Initialized;
        }

        /// <summary>
        /// Does the startup action.
        /// </summary>
        public void DoStartupAction()
        {
            if (this.startupActionExecuted)
            {
                return;
            }

            this.EnsureNotDisposed();

            this.startupActionExecuted = true;
            this.DoActionCore(ControllerMetadata.StartupActionName, null, false, ex => new ControllerException(string.Format(Resources.ExceptionInDoStartupAction, this.ControllerId), this.ControllerId, null, ex));
        }

        /// <summary>
        /// Does the action.
        /// </summary>
        /// <param name="actionName">Name of the action.</param>
        /// <param name="actionParameters">The action parameters.</param>
        public void DoAction(string actionName, params object[] actionParameters)
        {
            this.EnsureNotDisposed();

            this.DoActionCore(actionName, actionParameters, true, ex => new ControllerException(string.Format(Resources.ExceptionInDoAction, actionName, this.ControllerId), this.ControllerId, actionName, ex));
        }

        /// <summary>
        /// Determines whether this instance [can do action] the specified action name.
        /// </summary>
        /// <param name="actionName">Name of the action.</param>
        /// <param name="actionParameters">The action parameters.</param>
        /// <returns>
        ///   <c>true</c> if this instance [can do action] the specified action name; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="MyFramework.Core.ControllerException"></exception>
        public bool CanDoAction(string actionName, params object[] actionParameters)
        {
            this.EnsureNotDisposed();

            var item = this.MappedActions.FirstOrDefault(t => t.Item1 == actionName);
            if (item != null)
            {
                try
                {
                    return item.Item3(actionParameters);
                }
                catch (Exception ex)
                {
                    var handled = ExceptionHandler.HandleWithExceptionHandler(
                        ref ex,
                        ex1 => new ControllerException(string.Format(Resources.ExceptionInCanDoAction, actionName, this.ControllerId), this.ControllerId, actionName, ex1),
                        DefaultExceptionHandler);
                    if (!handled)
                    {
                        throw ex;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            else
            {
                throw new ControllerException(string.Format(Resources.ActionNotRegistered, actionName));
            }
        }

        /// <summary>
        /// Existses the action.
        /// </summary>
        /// <param name="actionName">Name of the action.</param>
        /// <returns></returns>
        public bool ExistsAction(string actionName)
        {
            this.EnsureNotDisposed();

            return this.MappedActions.Any(t => t.Item1 == actionName);
        }

        /// <summary>
        /// Tries the do action.
        /// </summary>
        /// <param name="actionName">Name of the action.</param>
        /// <param name="actionParameters">The action parameters.</param>
        /// <returns></returns>
        public bool TryDoAction(string actionName, params object[] actionParameters)
        {
            this.EnsureNotDisposed();

            if (this.ExistsAction(actionName) && this.CanDoAction(actionName, actionParameters))
            {
                this.DoAction(actionName, actionParameters);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Unloads the view.
        /// </summary>
        /// <param name="view">The view.</param>
        public void UnloadView(IView view)
        {
            this.EnsureNotDisposed();

            var executed = this.DoActionCore(ControllerMetadata.UnloadViewActionName, new object[] { view }, false, ex => new ControllerException(string.Format(Resources.ExceptionInUnloadViewAction, this.ControllerId), this.ControllerId, null, ex));
            if (!executed)
            {
                view.Dispose();
            }
        }

        /// <summary>
        /// Finalizes an instance of the <see cref="ControllerBase"/> class.
        /// </summary>
        ~ControllerBase()
        {
            this.Dispose(false);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (this.State < ControllerState.Disposing)
            {
                try
                {
                    this.State = ControllerState.Disposing;

                    if (this.ViewModel != null)
                    {
                        this.ViewModel.Views.Clear();
                    }
                    this.Dispose(true);
                }
                finally
                {
                    this.State = ControllerState.Disposed;
                }
            }
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
        }

        #endregion



    }
}
